﻿// <copyright file="DirectoryMapEntry.cs" company="Sinclair Community College">
// Copyright (c) Sinclair Community College. All rights reserved.
// </copyright>

namespace SinclairCC.AppV
{
    using System;
    using System.Diagnostics.CodeAnalysis;
    using System.Text;

    /// <summary>
    /// This class represents an entry in the directory map section of the CIDX header.
    /// </summary>
    public class DirectoryMapEntry
    {
        /// <summary>
        /// The internal version number for the directory entry.
        /// </summary>
        /// <remarks>
        /// This value is initialized to 1 and monotonically increases by 1 each time
        /// the file is updated.
        /// </remarks>
        private int version;
        
        /// <summary>
        /// The unique identifier (GUID) for the directory entry.
        /// </summary>
        /// <remarks>
        /// This entry is not case-sensitive. The first 4 bytes must uniquely identify
        /// this Directory Entry in a single SFT file.
        /// </remarks>
        private Guid id;
        
        /// <summary>
        /// The ID for this entry’s parent.
        /// </summary>
        /// <remarks>
        /// This is used as a back-link and is not case-sensitive.
        /// The root directory entry has the value "FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF".
        /// </remarks>
        private Guid parentId;
        
        /// <summary>
        /// The total size in bytes for this entry’s file-/directoryname in long filename
        /// format.
        /// </summary>
        /// <remarks>
        /// This value does not include a null terminator.
        /// </remarks>
        private short longFileNameSize;
        
        /// <summary>
        /// The total size in bytes for this entry’s file-/directoryname in short (8+3)
        /// filename format.
        /// </summary>
        /// <remarks>
        /// This value does not include a null terminator.
        /// </remarks>
        private short shortFileNameSize;
        
        /// <summary>
        /// Describes the type of the directory entry.
        /// </summary>
        private EntryType entryType;
        
        /// <summary>
        /// The total length, in bytes, of all the DATA block payloads for FB1 and FB2
        /// </summary>
        /// <remarks>
        /// For directories, this field is initialized to zero.
        /// </remarks>
        private int size;
        
        /// <summary>
        /// This field is reserved and should be initialized to all zeroes.
        /// </summary>
        [SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields", Justification = "This field is marked as reserved in the App-V File Format Specification.")]
        private byte[] reserved1;
        
        /// <summary>
        /// A bit field containing the file system attributes associated with the directory
        /// entry. See the Microsoft Windows SDK for a complete listing of FILE_ATTR_ attributes.
        /// </summary>
        private System.IO.FileAttributes fileSystemAttributes;
   
        /// <summary>
        /// Contains the  App-V–specific attributes associated with the directory entry.
        /// </summary>
        /// <remarks>
        /// This field is a bit field. These attributes control the behavior of the
        /// file/directory in the event of an upgraded package arriving on the client
        /// and/or when a file is branched on the client.
        /// </remarks>
        private AppVAttributes appVAttributes;
        
        /// <summary>
        /// This field is reserved and should be initialized to all zeroes.
        /// </summary>
        [SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields", Justification = "This field is marked as reserved in the App-V File Format Specification.")]
        private byte[] reserved2;

        /// <summary>
        /// The original creation timestamp for the file/directory, and it is stored as a
        /// Win32 FILETIME structure.
        /// </summary>
        /// <remarks>
        /// The format is the UTC (GMT) time zone.
        /// </remarks>
        private DateTime creationTimestamp;

        /// <summary>
        /// The last modification timestamp for the file/directory, and it is stored as a
        /// Win32 FILETIME structure.
        /// </summary>
        /// <remarks>
        /// The format is the UTC (GMT) time zone.
        /// </remarks>
        private DateTime modificationTimestamp;

        /// <summary>
        /// The last accessed timestamp for the file/directory.
        /// </summary>
        /// <remarks>
        /// In the SFT file, this value is stored as a Win32 FILETIME structure.
        /// The format is the UTC (GMT) time zone.
        /// For packages created with a directory structure from the FAT file system, this
        /// field is initialized to all zeroes.
        /// </remarks>
        private DateTime lastAccessedTimestamp;

        /// <summary>
        /// The security descriptor offset, relative to the security descriptor inside the CIDX section.
        /// </summary>
        /// <remarks>
        /// If the entry does not have a security descriptor attached to it, this field is initialized
        /// to zero. 
        /// </remarks>
        private short securityDescriptorOffset;

        /// <summary>
        /// The long filename for the directory entry.
        /// </summary>
        /// <remarks>
        /// The filename is stored as wide-char (double-byte) string, without the terminating null
        /// character (0x0000).
        /// </remarks>
        private string longFileName;

        /// <summary>
        /// The short (8+3 format) filename for the directory entry.
        /// </summary>
        /// <remarks>
        /// The filename is stored as wide-char (double-byte) string, without the terminating null
        /// character (0x0000).
        /// </remarks>
        private string shortFileName;

        /// <summary>
        /// Initializes a new instance of the DirectoryMapEntry class.
        /// </summary>
        /// <param name="fileReader">
        /// A binary file reader that points to the SFT file.
        /// </param>
        public DirectoryMapEntry(System.IO.BinaryReader fileReader)
        {
            this.version = fileReader.ReadInt32();

            byte[] guidBytes;

            guidBytes = fileReader.ReadBytes(16);
            this.id = new Guid(guidBytes);

            guidBytes = fileReader.ReadBytes(16);
            this.parentId = new Guid(guidBytes);

            this.longFileNameSize = fileReader.ReadInt16();        
            this.shortFileNameSize = fileReader.ReadInt16();
            this.entryType = (EntryType)fileReader.ReadInt32();        
            this.size = fileReader.ReadInt32();
        
            this.reserved1 = fileReader.ReadBytes(4);
        
            this.fileSystemAttributes = (System.IO.FileAttributes)fileReader.ReadInt32();
   
            this.appVAttributes = (AppVAttributes)fileReader.ReadInt16();
        
            this.reserved2 = fileReader.ReadBytes(8);

            long fileTime = fileReader.ReadInt64();
            this.creationTimestamp = DateTime.FromFileTimeUtc(fileTime);
            fileTime = fileReader.ReadInt64();
            this.modificationTimestamp = DateTime.FromFileTimeUtc(fileTime);
            fileTime = fileReader.ReadInt64();
            this.lastAccessedTimestamp = DateTime.FromFileTimeUtc(fileTime);

            this.securityDescriptorOffset = fileReader.ReadInt16();

            byte[] stringBytes;
            System.Text.UnicodeEncoding encoding = new UnicodeEncoding();

            stringBytes = fileReader.ReadBytes(this.longFileNameSize);
            this.longFileName = encoding.GetString(stringBytes);

            stringBytes = fileReader.ReadBytes(this.shortFileNameSize);
            this.shortFileName = encoding.GetString(stringBytes);
        }

        /// <summary>
        /// Gets the internal version number for the directory entry.
        /// </summary>
        /// <remarks>
        /// This value is initialized to 1 and monotonically increases by 1 each time
        /// the file is updated.
        /// </remarks>
        public int Version
        {
            get { return this.version; }
        }

        /// <summary>
        /// Gets the unique identifier (GUID) for the directory entry.
        /// </summary>
        /// <remarks>
        /// This entry is not case-sensitive. The first 4 bytes must uniquely identify
        /// this Directory Entry in a single SFT file.
        /// </remarks>
        public Guid ID
        {
            get { return this.id; }
        }

        /// <summary>
        /// Gets the ID for this entry’s parent.
        /// </summary>
        /// <remarks>
        /// This is used as a back-link and is not case-sensitive.
        /// The root directory entry has the value "FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF".
        /// </remarks>
        public Guid ParentID
        {
            get { return this.parentId; }
        }

        /// <summary>
        /// Gets the type of the directory entry.
        /// </summary>        
        public EntryType EntryType
        {
            get { return this.entryType; }
        }

        /// <summary>
        /// Gets the total length, in bytes, of all the DATA block payloads for FB1 and FB2.
        /// </summary>
        /// <remarks>
        /// For directories, this field is initialized to zero.
        /// </remarks>
        public int Size
        {
            get { return this.size; }
        }

        /// <summary>
        /// Gets the file system attributes associated with the directory entry.
        /// </summary>
        /// <remarks>
        /// See the Microsoft Windows SDK for a complete listing of FILE_ATTR_ attributes.
        /// </remarks>
        public System.IO.FileAttributes FileSystemAttributes
        {
            get { return this.fileSystemAttributes; }
        }

        /// <summary>
        /// Gets the App-V–specific attributes associated with the directory entry.
        /// </summary>
        /// <remarks>
        /// This field is a bit field. These attributes control the behavior of the
        /// file/directory in the event of an upgraded package arriving on the client
        /// and/or when a file is branched on the client.
        /// </remarks>
        public AppVAttributes AppVAttributes
        {
            get { return this.appVAttributes; }
        }

        /// <summary>
        /// Gets the original creation timestamp for the file/directory.
        /// </summary>
        /// <remarks>
        /// The format is the UTC (GMT) time zone.
        /// </remarks>
        public DateTime CreationTimestamp
        {
            get { return this.creationTimestamp; }
        }

        /// <summary>
        /// Gets the last modification timestamp for the file/directory.
        /// </summary>
        /// <remarks>
        /// The format is the UTC (GMT) time zone.
        /// </remarks>
        public DateTime ModificationTimestamp
        {
            get { return this.modificationTimestamp; }
        }

        /// <summary>
        /// Gets the last accessed timestamp for the file/directory.
        /// </summary>
        /// <remarks>
        /// The format is the UTC (GMT) time zone.
        /// For packages created with a directory structure from the FAT file system, this
        /// field is initialized to all zeroes.
        /// </remarks>
        public DateTime LastAccessedTimestamp
        {
            get { return this.lastAccessedTimestamp; }
        }

        /// <summary>
        /// Gets the security descriptor offset, relative to the security descriptor inside
        /// the CIDX section.
        /// </summary>
        /// <remarks>
        /// If the entry does not have a security descriptor attached to it, this field is initialized
        /// to zero. 
        /// </remarks>
        public short SecurityDescriptorOffset
        {
            get { return this.securityDescriptorOffset; }
        }

        /// <summary>
        /// Gets the long filename for the directory entry.
        /// </summary>
        public string LongFileName
        {
            get { return this.longFileName; }
        }

        /// <summary>
        /// Gets the short (8+3 format) filename for the directory entry.
        /// </summary>
        public string ShortFileName
        {
            get { return this.shortFileName; }
        }
    }
}
